#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <dlfcn.h>                               
#include <stdlib.h>

#include <errno.h>
#include <string.h>

#include <link.h>

#include <time.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/syscall.h>
#include "MemAllocLog.h"







////{替换malloc开始

static void* (*real_malloc)(size_t)=NULL;
static void (*real_free)(void*)=NULL;
static void * (*real_realloc) (void *memAddrOld, size_t memSize)=NULL;
static void * (*real_calloc) (size_t cnt, size_t memSize)=NULL;

extern char *program_invocation_name;
extern char *program_invocation_short_name;

char* typeStr(int p_type){
    char *type =  (p_type == PT_LOAD) ? "PT_LOAD" :
            (p_type == PT_DYNAMIC) ? "PT_DYNAMIC" :
            (p_type == PT_INTERP) ? "PT_INTERP" :
            (p_type == PT_NOTE) ? "PT_NOTE" :
            (p_type == PT_INTERP) ? "PT_INTERP" :
            (p_type == PT_PHDR) ? "PT_PHDR" :
            (p_type == PT_TLS) ? "PT_TLS" :
            (p_type == PT_GNU_EH_FRAME) ? "PT_GNU_EH_FRAME" :
            (p_type == PT_GNU_STACK) ? "PT_GNU_STACK" :
            (p_type == PT_GNU_RELRO) ? "PT_GNU_RELRO" : NULL;
    return type;
}
/**
 * ref:
 *   dl_iterate_phdr
 *   https://www.onitroad.com/jc/linux/man-pages/linux/man3/dl_iterate_phdr.3.html
 *   https://www.cnblogs.com/ysjblog/p/16340273.html
 */
static int save_single_so_addr_callback(struct dl_phdr_info *info, size_t size, void *data)
{
    FILE *outFilePtr=(FILE *)data;

    char *type;
    int p_type, j;

//    fprintf(outFilePtr,"Name: '%s' (%d segments)\n", info->dlpi_name,  info->dlpi_phnum);

    for (j = 0; j < info->dlpi_phnum; j++) {
//        Elf64_Phdr eleJ=info->dlpi_phdr[j];
        Elf64_Phdr * pEleJ=(info->dlpi_phdr)+j;
//        Elf64_Phdr * pEleJ=&(info->dlpi_phdr[j]);
        p_type = pEleJ->p_type;
        type =  typeStr(p_type);

        fprintf(outFilePtr,
//文件头
//dlpi_name, dlpi_phnum, dlpi_addr, p_vaddr, p_filesz, p_type, type, p_offset, p_paddr, p_align, p_memsz, p_flags
"%s, %d, 0x%Lx, 0x%Lx, 0x%Lx, 0x%Lx, %s, 0x%Lx, 0x%Lx, 0x%x, 0x%Lx, 0x%x\n",
info->dlpi_name,  info->dlpi_phnum, info->dlpi_addr ,pEleJ->p_vaddr, pEleJ->p_filesz, p_type, type, pEleJ->p_offset, pEleJ->p_paddr,pEleJ->p_align,  pEleJ->p_memsz, pEleJ->p_flags
);
//地址范围: 起点为 dlpi_addr+p_vaddr, 长度为 p_filesz 的地址范围
    }

    return 0;
}


/**保存已经加载的动态库地址
 * 注意：  save_so_list_addr 和 simple_save_so_list_addr 流程完全一致，没有提取只是为了不搞那么抽象而已
 * @param processName
 * @param pNow
 */
static void save_so_list_addr(char* processName,struct tm * pNow){
    //产生文件名
    char outFileName[PROC_NAME_LIMIT+HmDateTimeSz+48];
    genFileName(outFileName, "soList", processName, pNow);

    //打开文件
    FILE *outFilePtr=NULL;
    outFilePtr = fopen(outFileName, "w");
    if(outFilePtr == NULL)  {
        return;
    }

    //文件头
    fprintf(outFilePtr,"dlpi_name,dlpi_phnum,dlpi_addr,p_vaddr,p_filesz,p_type,type,p_offset,p_paddr,p_align,p_memsz,p_flags\n");

    //循环调用
    dl_iterate_phdr(save_single_so_addr_callback, outFilePtr);

    //关闭文件
    fclose(outFilePtr);
}


static int simple_save_single_so_addr_callback(struct dl_phdr_info *info, size_t size, void *data)
{
    FILE *outFilePtr=(FILE *)data;
    //文件头: dlpi_name,dlpi_addr
    fprintf(outFilePtr,"%s,0x%p\n", info->dlpi_name,  info->dlpi_addr);
    return 0;
}

/**保存已经加载的动态库地址（简单）
 * 注意：  save_so_list_addr 和 simple_save_so_list_addr 流程完全一致，没有提取只是为了不搞那么抽象而已
 * @param processName
 * @param pNow
 */
static void simple_save_so_list_addr(char* processName,struct tm * pNow){
    //产生文件名
    char outFileName[PROC_NAME_LIMIT+HmDateTimeSz+48];
    genFileName(outFileName, "soListSimple", processName, pNow);

    //打开文件
    FILE *outFilePtr=NULL;
    outFilePtr = fopen(outFileName, "w");
    if(outFilePtr == NULL)  {
        return;
    }

    //文件头
    fprintf(outFilePtr,"dlpi_name,dlpi_addr\n");

    //循环调用
    dl_iterate_phdr(simple_save_single_so_addr_callback, outFilePtr);

    //关闭文件
    fclose(outFilePtr);
}


/** 进程名字最大只保留128字符
 * 进程名处理为不多于给定长度 PROC_NAME_LIMIT
 * @return
 */
char* procNameLimited(){
    char* processName=program_invocation_short_name;
    if(NULL==processName || strlen(processName)>PROC_NAME_LIMIT){
        processName="";
    }
    return processName;
}

/**
 * 获取 粗略的时刻: 当前日期时刻
 * @return
 */
struct tm  getDatetimeCurrent(){
    //获取 粗略的时刻: 当前日期时刻
    time_t dtCur = time(NULL);
    struct tm datetimeCurrent = *localtime(&dtCur);
//    printf("now: %d-%02d-%02d %02d:%02d:%02d\n", datetimeCurrent.tm_year + 1900, datetimeCurrent.tm_mon + 1, datetimeCurrent.tm_mday, datetimeCurrent.tm_hour, datetimeCurrent.tm_min, datetimeCurrent.tm_sec);
    return datetimeCurrent;
}

static void wrap_gMA_save(void){

    //进程名字最大只保留128字符
    char* processName = procNameLimited();

//    //取当前 精准的时刻：毫秒数、纳秒数
//    struct timespec timespecCurrent;
//    clock_gettime(CLOCK_REALTIME, &(timespecCurrent));

    //获取 粗略的时刻: 当前日期时刻
    struct tm now = getDatetimeCurrent();

    //保存全局变量中的日志
    gMA_save(processName, &now);

    //保存已经加载的动态库地址
    save_so_list_addr(processName, &now);
    //保存已经加载的动态库地址（简单）
    simple_save_so_list_addr(processName, &now);
}
/**
 * 钩子初始化
 */
static void hook_init(void)
{
    //初始化gMA
    gMA_init();

    //记录原始函数地址
    real_malloc = (void* (*)(size_t)) dlsym(RTLD_NEXT, "malloc");
    real_free = (void (*)(void*)) dlsym(RTLD_NEXT, "free");
    real_realloc = (void * (*) (void *, size_t )) dlsym(RTLD_NEXT, "realloc");
    real_calloc = (void * (*) (size_t  , size_t  )) dlsym(RTLD_NEXT, "calloc");

    //如果原始函数找不到, 直接以 特殊退出码 退出 当前进程
    if (NULL == real_malloc || NULL  == real_free || NULL  == real_realloc || NULL  == real_calloc) {
//         fprintf(stderr, "Error in `dlsym`: %s\n", dlerror());  //fprintf内部估计也调用了malloc, 当前函数hook_init应该是被本文件中的malloc调用的，因此此处的fprintf有死递归可能，故而此处不要fprintf
        exit(99);
    }

    //当应用进程退出时, 保存已经记录到的MA日志
    atexit(wrap_gMA_save);
    //问题："保存动作 可能错过 进程退出中的free",  详细描述：
    //  atxit(f)添加进去的函数f, 最终在函数 __run_exit_handlers(, **listp, ...) 中被使用,
    //  (*listp)->fns 存储了 所有由atexit(f)加入的函数f们,  先加入的先执行,
    //  atexit(wrap_gMA_save)显然是最先加入的fns的, 因此最先执行，
    //    但这里想要wrap_gMA_save最后执行，因为最后执行才能捕捉到应用程序退出时的那些free动作。
}

//第1种 线程id : 函数pthread_self()返回值: 不适合 用作 唯一标识
//  函数pthread_self()返回值： 据说是同一进程内, 当线程1死后 其线程id会被后续线程复用, 因此 不适合 用作 唯一标识
pthread_t pthreadId(){
    pthread_t         pthreadId;
    pthreadId = pthread_self();
    return pthreadId;
}

//第2种 线程id:  系统调用SYS_gettid: 理论上 适合 用作 唯一标识
// 但系统调用有开销，可能要用threadLocal缓存
// 需要验证 有无threadLocal缓存时 结果是否一致
pid_t syscallGetTid()
{
    pid_t tid;
    tid=syscall(SYS_gettid);
    return tid;
}

pid_t syscallGetTid_threadLocalCache(){

    static __thread pid_t tid_cache = 0;
    if(tid_cache==0){
        tid_cache=syscallGetTid();
    }
    return tid_cache;
}

//第3种 线程id： 函数 gettid() 返回值, 不知道是谁的函数
pid_t gettid_lib(){
    pid_t pid=gettid();
    return pid;
}


/** 代理 malloc: 原malloc + 记录此次调用
 * @param memSize
 * @return
 */
void * malloc(size_t memSize)
{
    static __thread int malloc_times = 0;
    malloc_times++;

    if(real_malloc==NULL) {
        hook_init();
    }


//ref :  https://stackoverflow.com/questions/76067063/why-are-rbp-and-rax-invalid-operands-for-movl/76068101#76068101
//编译时必须加: -fno-omit-frame-pointer
    void ** rbp;
    __asm__("mov %%rbp, %0" : "=r" (rbp));
    void *callerAddr = *(rbp+1);

    void *memAddr = NULL;
    //调用原函数实现业务功能
    memAddr = real_malloc(memSize);
    

    if (malloc_times == 1)
    {
        pid_t threadId = syscallGetTid(); //A处
        //记录此次调用
        gMA_log(_FUNC_MALLOC, 1, memSize, memAddr, NULL, callerAddr, (unsigned long)threadId); //A处
    }	
    /*else if (malloc_times == 2)
    {
        //当计数器值*_times大于等于2时, 什么都不做, 用以：
        //斩断 A处(即syscallGetTid、gMA_log) 对 本代理 的再次调用
        //否则，会形成副作用(即死递归)
    }*/

	malloc_times = 0;
    return memAddr;
}

/** 代理 free:  原free + 记录此次调用
 * @param memAddr
 */
void free(void* memAddr)
{
    static __thread int free_times = 0;
    free_times++;

    if(real_free==NULL) {
        hook_init();
    }

//ref :  https://stackoverflow.com/questions/76067063/why-are-rbp-and-rax-invalid-operands-for-movl/76068101#76068101
//编译时必须加: -fno-omit-frame-pointer
    void ** rbp;
    __asm__("mov %%rbp, %0" : "=r" (rbp));
    void *callerAddr = *(rbp+1);

    real_free(memAddr);
    
    
    if (free_times == 1)
    {
        pid_t threadId = syscallGetTid(); //A处
        //记录此次调用
        gMA_log(_FUNC_FREE, 0, 0, memAddr, NULL, callerAddr, (unsigned long)threadId); //A处
    }	
    /*else if (free_times == 2)
    {
        //当计数器值*_times大于等于2时, 什么都不做, 用以：
        //斩断 A处(即syscallGetTid、gMA_log) 对 本代理 的再次调用
        //否则，会形成副作用(即死递归)
    }*/

	free_times = 0;
    return;
}
/** 代理 realloc:  原realloc + 记录此次调用
 * @param memAddrOld
 * @param memSize
 * @return
 */
void *realloc(void *memAddrOld, size_t memSize)
{
    static __thread int realloc_times = 0;
    realloc_times++;

    if(real_realloc==NULL) {
        hook_init();
    }

//ref :  https://stackoverflow.com/questions/76067063/why-are-rbp-and-rax-invalid-operands-for-movl/76068101#76068101
//编译时必须加: -fno-omit-frame-pointer
    void ** rbp;
    __asm__("mov %%rbp, %0" : "=r" (rbp));
    void *callerAddr = *(rbp+1);

    void *memAddr = NULL;
    memAddr = real_realloc(memAddrOld,memSize);
    
    if (realloc_times == 1)
    {
        pid_t threadId = syscallGetTid(); //A处
        //记录此次调用
        gMA_log(_FUNC_REALLOC, 1, memSize, memAddr, memAddrOld, callerAddr, (unsigned long)threadId); //A处
    }	
    /*else if (realloc_times == 2)
    {
        //当计数器值*_times大于等于2时, 什么都不做, 用以：
        //斩断 A处(即syscallGetTid、gMA_log) 对 本代理 的再次调用
        //否则，会形成副作用(即死递归)
    }*/

	realloc_times = 0;
    return memAddr;
}

/** 代理 calloc: 原calloc + 记录此次调用
 * @param cnt
 * @param memSize
 * @return
 */
void *calloc(size_t cnt, size_t memSize)
{
    static __thread int calloc_times = 0;
    calloc_times++;

    if(real_calloc==NULL) {
        hook_init();
    }

//ref :  https://stackoverflow.com/questions/76067063/why-are-rbp-and-rax-invalid-operands-for-movl/76068101#76068101
//编译时必须加: -fno-omit-frame-pointer
    void ** rbp;
    __asm__("mov %%rbp, %0" : "=r" (rbp));
    void *callerAddr = *(rbp+1);

    void *memAddr = NULL;
    memAddr = real_calloc(cnt,memSize);

    if (calloc_times == 1)
    {
        pid_t threadId = syscallGetTid(); //A处
        //记录此次调用
        gMA_log(_FUNC_CALLOC, cnt, memSize, memAddr, NULL, callerAddr, (unsigned long)threadId); //A处
    }	
    /*else if (calloc_times == 2)
    {
        //当计数器值*_times大于等于2时, 什么都不做, 用以：
        //斩断 A处(即syscallGetTid、gMA_log) 对 本代理 的再次调用
        //否则，会形成副作用(即死递归)
    }*/

	calloc_times = 0;
    return memAddr;
}
////替换malloc结束}